﻿/*
<File>
	<Copyright>Copyright © 2007, Outcoder. All rights reserved.</Copyright>
	<License see="//License.txt"/>
	<Owner Name="Daniel Vaughan" Email="danielvaughan@outcoder.com"/>
	<CreationDate>2007/11/19 20:00</CreationDate>
	<LastSubmissionDate>$Date: $</LastSubmissionDate>
	<Version>$Revision: $</Version>
</File>
*/

using System;
using System.Configuration;
using System.Net;

namespace Outcoder.Logging.Filters
{
	/// <summary>
	/// Restricts logging based on an IP address range.
	/// </summary>
	class IPAddressRangeFilter : FilterBase
	{
		uint begin;
		uint end;

		public IPAddressRangeFilter()
		{
			Init += IPAddressRangeFilter_Init;
		}

		void IPAddressRangeFilter_Init(object sender, FilterInitEventArgs e)
		{
			ArgumentValidator.AssertNotNull(e, "e");
			ArgumentValidator.AssertNotNull(e.ConfigurationElement, "e.ConfigurationElement");

			/* Reset state. */
			begin = end = 0;

			var beginAttribute = e.ConfigurationElement.Attributes["Begin"];
			if (beginAttribute == null)
			{
				throw new ClientLoggingException("Begin ip address attribute does not exists."); /* TODO: Make localizable resource. */
			}

			try
			{
				begin = ToUInt(IPAddress.Parse(beginAttribute.Value));
			}
			catch (Exception ex)
			{
				throw new ClientLoggingException("Begin ip address is not a valid IP address.", ex); /* TODO: Make localizable resource. */
			}

			var endAttribute = e.ConfigurationElement.Attributes["End"];
			if (endAttribute == null)
			{
				throw new ClientLoggingException("End ip address attribute does not exists."); /* TODO: Make localizable resource. */
			}

			try
			{
				end = ToUInt(IPAddress.Parse(endAttribute.Value));
			}
			catch (Exception ex)
			{
				throw new ClientLoggingException("End ip address is not a valid IP address.", ex); /* TODO: Make localizable resource. */
			}
            
			if (Action == FilterAction.Default)
			{
				Action = FilterAction.Allow;
			}
			if (Action != FilterAction.Allow && Action != FilterAction.Deny)
			{
				throw new ConfigurationErrorsException(InvalidActionMessage);
			}
		}

		public override bool IsValid(LogEntryOrigin origin, IClientInfo info)
		{
			string addressValue = info.IPAddress;

			bool withinRange = string.IsNullOrEmpty(addressValue) || IsWithinRange(begin, addressValue, end);
			switch (Action)
			{
				case FilterAction.Allow:
					return withinRange;
				case FilterAction.Deny:
					return !withinRange;
			}
			
			throw new ConfigurationErrorsException(InvalidActionMessage);
		}

		/// <summary>
		/// Determines whether the specified addressValue IP address 
		/// falls within the range specified by beginAddress and endAddress.
		/// </summary>
		/// <param name="beginAddress">The begin address.</param>
		/// <param name="addressValue">The address value to test.</param>
		/// <param name="endAddress">The end address.</param>
		/// <returns>
		/// 	<c>true</c> if the addressValue is within the specified range; otherwise, <c>false</c>.
		/// </returns>
		static bool IsWithinRange(uint beginAddress, string addressValue, uint endAddress)
		{
			IPAddress address;
			if (!IPAddress.TryParse(addressValue, out address))
			{
				return false;
			}
			uint ip = ToUInt(address);
			return ip >= beginAddress && ip <= endAddress;
		}

		/// <summary>
		/// Converts and <see cref="IPAddress"/> to an unsigned int.
		/// </summary>
		/// <param name="ipAddress">The ip address to convert.</param>
		/// <returns>A <code>uint</code> representing 
		/// the specified ipAddress.</returns>
		static uint ToUInt(IPAddress ipAddress)
		{
			byte[] bytes = ipAddress.GetAddressBytes();
			
			uint result = (uint)bytes[0] << 24;
			result += (uint)bytes[1] << 16;
			result += (uint)bytes[2] << 8;
			result += bytes[3];

			return result;
		}
	}
}
